5.1.2 node 常见面试题

1.Node 异步单线程原理

Node.js 的结构

Node.js 标准库,这部分是由 Javascript编写的,即我们使用过程中直接能调用的 API。在源码中的 lib 目录下可以看到。

Node bindings,这一层是 Javascript与底层 C/C++ 能够沟通的关键,前者通过 bindings 调用后者,相互交换数据。
实现在 node.cc 这一层是支撑 Node.js 运行的关键,由 C/C++ 实现。

V8:Google 推出的 Javascript VM,也是 Node.js 为什么使用的是 Javascript的关键,  
它为 Javascript提供了在非浏览器端运行的环境,它的高效是 Node.js 之所以高效的原因之一。

Libuv:它为 Node.js 提供了跨平台,线程池,事件池,异步 I/O 等能力,是 Node.js 如此强大的关键。

C-ares:提供了异步处理 DNS 相关的能力。

http_parser、OpenSSL、zlib 等:提供包括 http 解析、SSL、数据压缩等其他的能力。
1
2
3
4
5
6
7
8
9
10
11
12
13

与操作系统交互

举个简单的例子,我们想要打开一个文件,并进行一些操作,可以写下面这样一段代码:

var fs = require('fs');
fs.open('./test.txt', "w", function(err, fd) {    //..do something});
1
2

这段代码的调用过程大致可描述为:lib/fs.js → src/node_file.cc → uv_fs

单线程

Node.js的单线程指的是主线程是“单线程”,由主要线程去按照编码顺序一步步执行程序代码,
假如遇到同步代码阻塞,主线程被占用,后续的程序代码执行就会被卡住。

为什么一个单线程的效率可以这么高,同时处理数万级的并发而不会造成阻塞呢?就是我们下面所说的--------事件驱动。

事件驱动/事件循环

1、每个Node.js进程只有一个主线程在执行程序代码,形成一个执行栈(execution context stack)。
2、主线程之外,还维护了一个"事件队列"(Event queue)。当用户的网络请求或者其它的异步操作到来时,node都会把它放到Event Queue之中,  
  此时并不会立即执行它,代码也不会被阻塞,继续往下走,直到主线程代码执行完毕。
3、主线程代码执行完毕完成后,然后通过Event Loop,也就是事件循环机制,开始到Event Queue的开头取出第一个事件,  
  从线程池中分配一个线程去执行这个事件,接下来继续取出第二个事件,再从线程池中分配一个线程去执行,然后第三个,第四个。  
  主线程不断的检查事件队列中是否有未执行的事件,直到事件队列中所有事件都执行完了,  
  此后每当有新的事件加入到事件队列中,都会通知主线程按顺序取出交EventLoop处理。  
  当有事件执行完毕后,会通知主线程,主线程执行回调,线程归还给线程池。
4、主线程不断重复上面的第三步。
1
2
3
4
5
6
7
8
9

我们所看到的node.js单线程只是一个js主线程,本质上的异步操作还是由线程池完成的,
node将所有的阻塞操作都交给了内部的线程池去实现,
本身只负责不断的往返调度,并没有进行真正的I/O操作, 从而实现异步非阻塞I/O,
这便是node单线程和事件驱动的精髓之处了。

libuv

Node.js采用V8作为js的解析引擎,而I/O处理方面使用了自己设计的libuv,
libuv是一个基于事件驱动的跨平台抽象层,封装了不同操作系统一些底层特性,对外提供统一的API,
事件循环机制也是它里面的实现

Event Loop的执行顺序:

HTML5标准规定了setTimeout()的第二个参数的最小值(最短间隔),不得低于4毫秒,如果低于这个值,就会自动增加

每次事件循环都包含了6个阶段,对应到 libuv 源码中的实现

timers 阶段:这个阶段执行timer(setTimeout、setInterval)的回调

I/O callbacks 阶段:执行一些系统调用错误,比如网络通信的错误回调

idle, prepare 阶段:仅node内部使用

poll 阶段:获取新的I/O事件, 适当的条件下node将阻塞在这里

check 阶段:执行setImmediate()的回调

close callbacks 阶段:执行socket的close事件回调。
1
2
3
4
5
6
7
8
9
10
11

2.Node 如何多进程

3.co 模块原理

那么co就有两种实现方式,promise或者thunk。co4.0之前是用thunk实现的,之后是用promise实现的。
一种机制,在一个异步操作执行完毕以后通知下一个异步操作开始执行

4.node 的特点

  • 异步 I/O
  • 事件循环
  • 单线程
  • 跨平台

node 引入模块

优先从缓存加载

  • 文件定位
  • 路径分析
  • 编译执行

epoll 是Linux内核为处理大批量文件描述符而作了改进的 poll
poll: 在一定时间内轮流看一遍里面有没有数据要读写

node 异步 I/O 模型基本要素

  • 事件循环
  • 观察者
  • 请求对象
  • I/O 线程池

node 非异步 I/O API

  • setTimeout
  • setInterval
  • setImmediate
  • process.nextTick()

node 异步编程解决方案

  • 事件发布/订阅者模式
  • Promise/Deferred 模式
  • 流程控制库

++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++++++++++++

1.什么是错误优先的回调函数?

错误优先的回调函数用于传递错误和数据。第一个参数始终应该是一个错误对象, 用于检查程序是否发生了错误。其余的参数用于传递数据。例如:

fs.readFile(filePath, function(err, data) {  
    if (err) {
        //handle the error
    }
    // use the data object
});
1
2
3
4
5
6

2.如何避免回调地狱

你可以有如下几个方法:

模块化:将回调函数分割为独立的函数
使用Promises
使用yield
来计算生成器或Promise
1
2
3
4

3.如何用Node监听80端口

这题有陷阱!在类Unix系统中你不应该尝试去监听80端口,因为这需要超级用户权限。 因此不推荐让你的应用直接监听这个端口。

目前,如果你一定要让你的应用监听80端口的话,你可以有通过在Node应用的前方再增加一层反向代理 (例如nginx)来实现

4.什么是事件循环

Node采用的是单线程的处理机制(所有的I/O请求都采用非阻塞的工作方式),
至少从Node.js开发者的角度是这样的。
而在底层,Node.js借助libuv来作为抽象封装层, 从而屏蔽不同操作系统的差异,
Node可以借助livuv来来实现多线程。

Libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个事件循环, 以异步的方式将任务的执行结果返回给V8引擎。

Node.js的运行机制如下。

(1)V8引擎解析JavaScript脚本。

(2)解析后的代码,调用Node API。

(3)libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。

(4)V8引擎再将结果返回给用户。
1
2
3
4
5
6
7
8
9

核心概念

  • Chrome V8 是 Google 发布的开源 JavaScript 引擎,采用 C/C++ 编写,在 Google 的 Chrome 浏览器中被使用。
    Chrome V8 引擎可以独立运行,也可以用来嵌入到 C/C++ 应用程序中执行。
  • Event Loop 事件循环(由 libuv 提供)
  • Thread Pool 线程池(由 libuv 提供)

5.什么是Stub?举个使用场景

Stub是用于模拟一个组件或模块的函数或程序。在测试用例中, 简单的说,
你可以用Stub去模拟一个方法,从而避免调用真实的方法, 使用Stub你还可以返回虚构的结果。你可以配合断言使用Stub。 举个例子,在一个读取文件的场景中,当你不想读取一个真正的文件时:

单元测试

单元测试根据主流的分类可以分成两类,分别是BDD和TDD

6.什么是测试金字塔?

测试金字塔指的是: 当我们在编写测试用例时,底层的单元测试应该远比上层的端到端测试要多。

参考